#pragma once

#include <glibmm/ustring.h>
#include <glibmm/iochannel.h>
#include <stdio.h>

#include "Exception.hpp"

namespace BuildNG
{
  class InStream
  {
  public:
    static const int max_buffer_size = 2 * 1024 * 1024;

    InStream(int buffer_size = 1024);
    virtual ~InStream();

    void      read_buffer(void *dest,int byte_size);

    guint32   read_uint32();
    gint32    read_int32();
    guint16   read_uint16();
    gint16    read_int16();
    guint8    read_uint8();
    gint8     read_int8();
    bool      read_bool();

    bool      read_line(Glib::ustring &line);
    
    virtual Glib::ustring   get_stream_desc() const { return ""; }

  protected:
    virtual size_t read_impl(char *buffer,size_t max_size) = 0;

  private:
    // size of the buffer memory block.
    size_t    allocated_buffer_size;
    // size of the actual data that's currently in buffer. Most
    // of the time, this is the same as allocated_buffer_size, 
    // but for example, when there are not enough bytes remaining
    // to fill the whole buffer, this value will be smaller than 
    // allocated_buffer_size
    char*     cur_buffer_end;
    char*     buffer;

    char*     read_ptr;
    
    bool      ended_with_newline;

    void fill_buffer();
  };
  
  class MemoryInStream : public InStream
  {
  public:
    enum Flags
    {
      Flag_DeleteData =       1 << 0,
    };

    MemoryInStream(const void *data,size_t data_size,int flags,int buffer_size = 1024);
    MemoryInStream(const gchar *null_term_string);
    ~MemoryInStream();

  protected:
    virtual size_t read_impl(char *buffer,size_t max_size);

  private:
    const char* data;
    int         flags;

    const char* read_ptr;
    size_t      size_left;
  };

  class FileInStream : public InStream
  {
  public:
    FileInStream(const Glib::ustring &file_name);
    ~FileInStream();
    
    virtual Glib::ustring get_stream_desc() const;
    
  protected:
    virtual size_t read_impl(char *buffer,size_t max_size);
    
  private:
    FILE*             file;
    Glib::ustring     file_name;
  };
  
  class GIOChannelInStream : public InStream
  {
  public:
    GIOChannelInStream(Glib::RefPtr<Glib::IOChannel> io_channel)
      : io_channel(io_channel)
    {
    }
    
    virtual Glib::ustring get_stream_desc() const;

  protected:
    virtual size_t read_impl(char *buffer,size_t max_size);

  private:
    Glib::RefPtr<Glib::IOChannel> io_channel;
  };

  class StdInStream : public InStream
  {
  public:
    StdInStream(std::istream *stream,bool take_ownership);
    ~StdInStream();

  protected:
    virtual size_t read_impl(char *buffer,size_t max_size);

  private:
    std::istream*   stream;
    bool            owned;
  };
  
  /////////////
  // Exceptions
  class InStreamException : public Exception
  {
  public:
    InStreamException(const Glib::ustring &msg,const InStream &stream);
    
  private:
    static Glib::ustring create_message(const Glib::ustring &msg,const InStream &stream);
  };
  
  class InputPastEndOfFileException : public InStreamException
  {
  public:
    InputPastEndOfFileException(const InStream &stream);
  };

  class FileNotFoundException : public Exception
  {
  public:
    FileNotFoundException(const Glib::ustring &file_name);

  private:
    static Glib::ustring create_message(const Glib::ustring &file_name);
  };
  
  class InStreamInvalidUTF8Exception : public InStreamException
  {
  public:
    InStreamInvalidUTF8Exception(const InStream &stream);
  };
}
